package kb;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import option.KBVocabulary;

import utils.exception.CoreReasoningException;
import utils.exception.SyntaxParsingException;

import entities.axiom.RoleAxiom;
import entities.axiom.RoleInclusionAxiom;
import entities.concept.Concept;
import entities.role.Role;

/**
 * Assume RBox is a part of a TBox (containing role inclusion axioms, information on inverse/transitive roles, etc.)
 * @author j55wu
 *
 */
public class RBox {
	public static final boolean resolveTransitiveRoles = true; //if true, then axioms are added to eliminate transitive roles
	private Set<RoleAxiom> originalAxioms;
	//simple roles (role names or inverse)
	private Map<Role, Role> inverseMaps = new HashMap<Role, Role>();
	//this indexes the set of role inclusions
	private Map<Role, Set<Role>> superRoles;
	private Map<Role, Set<Role>> subRoles;
	private Set<Role> transRoles;
	//a complex role is a transitive role, or of which there is a transitive subrole
	private Set<Role> complexRoles; 
	
	private boolean compileStatus = false;
	private Map<Role, Concept>  featureDomain; //simulate datatype property domain, note a feature is in fact NOT a datatype property
	
	public RBox() {
		this.originalAxioms = new HashSet<RoleAxiom> ();
		this.superRoles = new HashMap<Role, Set<Role>>();
		this.subRoles = new HashMap<Role, Set<Role>>();
		this.featureDomain = new HashMap<Role, Concept>();
		this.transRoles = new HashSet<Role>();
		this.complexRoles = new HashSet<Role>();
	}
	
	public Map<Role, Role> getInverseMap(){
		return inverseMaps;
	}
	
	public void setInverseMap(Map<Role, Role> map){
		this.inverseMaps = map;
	}
	
	public Set<Role> getTransRoles(){
		return this.transRoles;
	}
	
	public boolean isTransitive(Role r){
		return this.transRoles.contains(r);
	}
	
	public boolean isComplexRole(Role r){
		return this.complexRoles.contains(r);
	}
	
	//data type domain restriction
	public final void addFeatureDomain(Role f, Concept d) throws SyntaxParsingException{
		if(featureDomain.containsKey(f)){
			throw new SyntaxParsingException("Ignored: duplicate domain for datatype property: "+f);
		}
		featureDomain.put(f, d);
	}
	
	public Map<Role, Concept> getFeatureDomain(){
		return this.featureDomain;
	}
	
	
	//inverse
	public boolean hasInverseRole(Role r) {
		return inverseMaps.containsKey(r);
	}

//	public void clearInverseMaps() {
//		inverseMaps.clear();
//	}
	
	public Role getInverseRole(Role r) {
		if (inverseMaps.containsKey(r)) {
			Role ir= inverseMaps.get(r);
//			if(!inverseMaps.containsKey(ir)){
//				System.err.println("one side found, missing :"+ir+" -> "+r);
//			}
			return ir;
		}
		//if the given R is a primitive name, create this role and its inverse, and add as inverses
		String newname = null;
		if (r.getRoleName().startsWith(KBVocabulary.INV_ROLE_PREFIX)) {
			newname = r.getRoleName().substring(KBVocabulary.INV_ROLE_PREFIX
					.length());
		} else {
			newname = KBVocabulary.INV_ROLE_PREFIX + r.getRoleName();
		}
		Role ri = Role.createRoleAsIs(newname);
		try {
			setInverseRoles(r, ri);
		} catch (CoreReasoningException e) {
			e.printStackTrace();
		}
		return ri;
	}

	public void setInverseRoles(Role r1, Role r2)
			throws CoreReasoningException {
//		System.err.println("using rbox: "+this.hashCode());
		if (inverseMaps.containsKey(r1)) {
			Role pr=inverseMaps.get(r1);
			if (!pr.equals(r2)) {
				throw new CoreReasoningException("Ignoring duplicate inverse role:" + r1
						+ " -> " + r2+"; already has "+pr);
			}
		} else {
			inverseMaps.put(r1, r2);
		}
		if (inverseMaps.containsKey(r2)) {
			if (!inverseMaps.get(r2).equals(r1)) {
				Role pr=inverseMaps.get(r2);
				throw new CoreReasoningException("Ignoring duplicate inverse role declaration:" + r2
						+ " -> " + r1+"; already has "+pr);
			}
		} else {
			inverseMaps.put(r2, r1);
		}
		return;
	}
	

	//note that this method returns different results before/after compilation
	public final Set<Role> getAllSuperRoles(Role r){
		return this.superRoles.get(r);
	}
	public final Set<Role> getAllSubRoles(Role r){
		return this.subRoles.get(r);
	}
	
	public final Map<Role, Set<Role>> getSuperRolesMap(){
		return this.superRoles;
	}
	public final Map<Role, Set<Role>> getSubRolesMap(){
		return this.subRoles;
	}
	// Compilation: for each role, get all its super roles (reachability)
	// deposit all subroles
	public void compileRBox(){
		if(compileStatus){
			return;
		}
		//
		for(Role sb : this.superRoles.keySet()){
			//get all super role by reachability
			Set<Role> allsp = DFS(sb, new HashSet<Role>());
			this.superRoles.get(sb).addAll(allsp);
//			System.out.println(sb+" < "+allsp);
			//add corresponding sub roles
			for(Role sp:allsp){
				//add it self, then add subs
				insertInvertedSubRoles(sp, sp);
				insertInvertedSubRoles(sp, sb);
			}
		}
		//add all complex roles:if this role is a transitive, all its superroles become complex
		this.complexRoles.addAll(this.transRoles);
		for(Role sb : this.superRoles.keySet()){
			if(this.transRoles.contains(sb)){
				this.complexRoles.addAll(this.superRoles.get(sb));
			}
		}
//		System.out.println(" all complex role: "+complexRoles);
		compileStatus = true;
	}
	
	// get all the successors of a given root
	private Set<Role> DFS(Role root, Set<Role> given) {
		given.add(root);
		if(!this.superRoles.containsKey(root)){
			return given;
		}
		for (Role v : this.superRoles.get(root)) {
			if (!given.contains(v)) {
				given = DFS(v, given);
			}
		}
		return given;
	}
	
//	private void populateSubRoles(){
//		for(Role sb : this.superRoles.keySet()){
//			for(Role sp:this.superRoles.get(sb)){
//				insertSubRoles(sp, sb);
//			}
//		}
//	}
	
	//R is transitive, so is inv-R
	public void insertTransRole(Role r, boolean addInverse){
		this.transRoles.add(r);
		if(addInverse){
			this.transRoles.add(getInverseRole(r));
		}
	}
	
	//i.e., R > S
	private void insertInvertedSubRoles(Role rl, Role rr){
		Set<Role> sub;
		if(this.subRoles.containsKey(rl)){
			sub = this.subRoles.get(rl);
			sub.add(rr);
		}else{
			sub = new HashSet<Role>();
			sub.add(rr);
			this.subRoles.put(rl, sub);
		}
	}
	
	//i.e., the standard R < S
	protected void insertRoleInclusionAxiom(Role rl, Role rr){
		Set<Role> sup;
		if(this.superRoles.containsKey(rl)){
			sup = this.superRoles.get(rl);
			sup.add(rr);
		}else{
			sup = new HashSet<Role>();
			sup.add(rr);
			this.superRoles.put(rl, sup);
		}
		
	}

	
	@Override
	public String toString(){
		StringBuilder sb = new StringBuilder();
		this.save(sb);
		return sb.toString();
//		String ret = "";
//		ret += "\n=============\nAxioms in the RBox\n=============\n";
//		for(Role sb : this.superRoles.keySet()){
//			for(Role sp:this.superRoles.get(sb)){
//				System.out.println(sb+" -> "+sp);
//			}
//		}
//		return ret;
	}
	
	public void save(StringBuilder sb) {
		sb.append("######### Role inclusions ###########\n");
		for(RoleAxiom ra:this.originalAxioms){
			sb.append(ra.toString()); 
			sb.append("\n");
		}
		return;
	}
	
	public void saveDetailed(StringBuilder sb) {
		sb.append("######### Role inclusions ###########\n");
		for(Role sub : this.superRoles.keySet()){
			for(Role sp:this.superRoles.get(sub)){
				if(sp.equals(sub)){
					continue;
				}
				sb.append("("+KBVocabulary.ROLEAXIOM+" ");
				sb.append(sub.toSerializedString());
				sb.append(" ");
				sb.append(sp.toSerializedString() +")\n");
			}
		}
		sb.append("######### Inverse Roles (with implied ones) ###########\n");
		Set<Role> rep = new HashSet<Role>();
		for(Role r:inverseMaps.keySet()){
			if(rep.contains(r)){
				continue;
			}
			sb.append("("+KBVocabulary.ROLE_INV+" ");
			sb.append(r.toSerializedString());
			sb.append(" ");
			Role ir = inverseMaps.get(r);
			rep.add(ir);
			sb.append(ir.toSerializedString() +")\n");
		}
		sb.append("######### Transitive roles ###########\n");
		//add transitive roles
		rep = new HashSet<Role>();
		for(Role r:this.transRoles){
			if(rep.contains(r)){
				continue;
			}
			Role ir = inverseMaps.get(r);
			rep.add(ir);
			sb.append("("+KBVocabulary.ROLE_TRANS+" ");
			sb.append(r.toSerializedString());
			sb.append(" )\n");
		}
		sb.append("######### Feature domains ###########\n");
		//add domain info for features
		for(Role f:this.featureDomain.keySet()){
			sb.append("("+KBVocabulary.FEAT_DOM+" ");
			sb.append(f.toSerializedString());
			sb.append(" ");
			Concept dom = this.featureDomain.get(f);
			sb.append(dom.toSerializedString() +")\n");
		}
		return;
	}

	public Set<RoleAxiom> getOriginalAxioms() {
		return originalAxioms;
	}
	
	public void clearOriginalAxioms() {
		this.originalAxioms.clear();
	}

	public void addOriginalAxioms(RoleAxiom ax) {
		this.originalAxioms.add(ax);
	}

}
